//Decoder states.
const decoderStateIdle = 0;
const decoderStateInitializing = 1;
const decoderStateReady = 2;
const decoderStateFinished = 3;

//Player states.
const playerStateIdle = 0;
const playerStatePlaying = 1;
const playerStatePausing = 2;

//Constant.
const maxBufferTimeLength = 1.0;
const downloadSpeedByteRateCoef = 2.0;

String.prototype.startWith = function (str) {
    var reg = new RegExp("^" + str);
    return reg.test(this);
};

function FileInfo(url) {
    this.url = url;
    this.size = 0;
    this.offset = 0;
    this.chunkSize = 65536;
}

function Player() {
    this.fileInfo = null;
    this.canvas = null;

    this.pcmPlayer = null;
    this.audioEncoding = "";
    this.audioChannels = 0;
    this.audioSampleRate = 0;
    this.audioFrameBuffer = null;
    this.audioBaseFrameTime = 0;
    this.audioBaseTime = 0;
    this.audioLastFrameTime = 0;

    this.webglPlayer = null;
    this.callback = null;
    this.waitBufferLength = 524288;

    this.videoFrameBuffer = null;
    this.videoBaseFrameTime = 0;
    this.videoBaseTime = 0;
    this.duration = 0;
    this.pixFmt = 0;
    this.videoWidth = 0;
    this.videoHeight = 0;
    this.yLength = 0;
    this.uvLength = 0;
    
    this.playerState = playerStateIdle;
    this.decoderState = decoderStateIdle;
    this.decoderError = 0;
    this.buffering = false;

    this.videoMode = kVideoVodMode;
    this.downloadProto = kProtoHttp;
    this.downloadOffset = -1;
    this.downloadSeqNo = 0;

    this.decodeInterval = 5;
    this.chunkInterval = 200;
    this.timeLabel = null;
    this.timeTrack = null;
    this.trackTimer = null;
    this.trackTimerInterval = 500;
    this.displayDuration = "00:00:00";

    this.seeking = false;  // Flag to preventing multi seek from track.
    this.seekWaitLen = 524288; // Default wait for 512K, will be updated in onVideoParam.
    this.seekReceivedLen = 0;


    this.accelerate_start_time = 5000;     // 直播时最大的缓存视频帧时间，超过此值就要加速播放，以此来减少直播延时
    this.accelerate_stop_time = 1500;
    this.accelerate_play_frames = 3;
    this.accelerate_skip_frames = 2;
    this.play_accelerate = false;       // 播放加速
    this.max_timestamp = 0;
    this.curr_timestamp = 0;
    this.skip_count = 0;

    this.timestamp_inc = 0.0;

    this.isFirst = 1;
    this.durationLabel = null;

    //callback
    this.onTimeupdate = null;
    this.onLoadstart = null;
    this.onPlaying = null;
    this.onPause = null;
    this.onStop = null;
    this.onReadyToPlay = null;
    this.onLiveFinish = null;
    this.muteCallback = null;

    this.loadingDiv = null;
    this.logger = new Logger("Player");

    this.initDownloadWorker();
    this.initDecodeWorker();
}

Player.prototype.initDownloadWorker = function () {
    var self = this;
    this.downloadWorker = new Worker("./player/js/player/downloader.js");
    this.downloadWorker.onmessage = function (evt) {
        var objData = evt.data;
        switch (objData.t) {
            case kGetFileInfoRsp:
                self.onGetFileInfo(objData.i);
                break;
            case kFileData:
                self.onFileData(objData.d, objData.s, objData.e, objData.q);
                break;
            case kDownloadStopped:
                self.onDownloadStopped(objData.o, objData.c, objData.s, objData.n);   // (offset, status)
                break;
        }
    }
};

Player.prototype.initDecodeWorker = function () {
    var self = this;

    this.decodeWorker = new Worker("./player/js/player/decoder.js");
    this.decodeWorker.onmessage = function (evt) {
        var objData = evt.data;
        switch (objData.t) {
            case kInitDecoderRsp:
                self.onInitDecoder(objData);
                break;
            case kOpenDecoderRsp:
                self.onOpenDecoder(objData);
                break;
            case kVideoFrame:
                self.onVideoFrame(objData);
                break;
            case kAudioFrame:
                self.onAudioFrame(objData);
                break;
            case kRequestDataEvt:   // from Decoder
                self.onRequestData(objData.o, objData.a, objData.s);
                break;
            case kSeekToRsp:
                self.onSeekToRsp(objData.r, objData.ms);
                break;
            case kDecodeRsp:
                self.onDecodeRsp(objData.e, objData.s);
                break;
            case kChangeSourceRsp:
                self.onChangeSourceRsp(objData.e);
                break;
            case kPauseDecodeRsp:
                self.onPauseDecodeRsp();
                break;
            case kResumeDecodeRsp:
                self.onResumeDecodeRsp();
                break;
        }
    }
};

/**
 * 设置直播加速参数，减少直播延时
 * @param {*} start_time    加速开始时间，单位毫秒，缓存帧数的总时间超过此值时，启动加速播放
 * @param {*} stop_time     加速停止时间，单位毫秒，缓存帧数的总时间小于此值时，停止加速播放
 * @param {*} play_frames   启动加速后，每次连续播放的视频帧数，超过此值就丢帧
 * @param {*} skip_frames   启动加速后，每次丢帧的帧数
 */
Player.prototype.setAcceleration = function(start_time, stop_time, play_frames, skip_frames) {
    this.accelerate_start_time = start_time;
    this.accelerate_stop_time = stop_time;
    this.accelerate_play_frames = play_frames;
    this.accelerate_skip_frames = skip_frames;
}

/**
 * 
 * @param {*} url 
 * @param {*} canvas 
 * @param {*} callback 
 * @param {*} waitBufferLength 
 * @param {*} videoMode 视频模式，kVideoVodMode-点播, kVideoLiveMode-直播，kVideoRealtimeLiveMode-实时直播
 */
Player.prototype.play = function (url, canvas, callback, waitBufferLength, videoMode) {
    this.isFirst = 1;
    this.logger.logInfo("Play " + url + ".");

    var ret = {
        e: 0,
        m: "Success"
    };

    var success = true;
    do {
        if (this.fileInfo != null && this.fileInfo.url != url && this.playState != playerStateIdle) {
            this.stop();
        }
        if (this.playerState == playerStatePausing) {
            ret = this.resume();
            break;
        }

        if (this.playerState == playerStatePlaying) {
            break;
        }

        if (!url) {
            ret = {
                e: -1,
                m: "Invalid url"
            };
            success = false;
            this.logger.logError("[ER] playVideo error, url empty.");
            break;
        }

        if (!canvas) {
            ret = {
                e: -2,
                m: "Canvas not set"
            };
            success = false;
            this.logger.logError("[ER] playVideo error, canvas empty.");
            break;
        }

        if (!this.downloadWorker) {
            ret = {
                e: -3,
                m: "Downloader not initialized"
            };
            success = false;
            this.logger.logError("[ER] Downloader not initialized.");
            break
        }

        if (!this.decodeWorker) {
            ret = {
                e: -4,
                m: "Decoder not initialized"
            };
            success = false;
            this.logger.logError("[ER] Decoder not initialized.");
            break
        }

        if (url.startWith("ws://") || url.startWith("wss://")) {
            this.downloadProto = kProtoWebsocket;
        } else if (url.startWith("rtmp://")) {
            this.downloadProto = kProtoRtmp;        // RTMP协议
        } else {
            this.downloadProto = kProtoHttp;
        }

        this.fileInfo = new FileInfo(url);
        this.canvas = canvas;
        this.callback = callback;
        this.videoMode = videoMode;
        this.waitBufferLength = (this.downloadProto == kProtoRtmp) ? 0 : (waitBufferLength || this.waitBufferLength);
        this.playerState = playerStatePlaying;
        this.decoderRequesting = false;
        this.videoBaseTime = 0;
        this.audioBaseTime = 0;
        this.lastDisplayTime = 0;
        this.audio_skipframes = 0;
        this.video_skipframes = 0;
        this.audio_timestamp_inc = 0;
        this.video_timestamp_inc = 0;

        this.timestamp_inc = 0.0;

        //var playCanvasContext = playCanvas.getContext("2d"); //If get 2d, webgl will be disabled.
        this.webglPlayer = new WebGLPlayer(this.canvas, {
            preserveDrawingBuffer: false
        });
        this.startTrackTimer();
        this.displayLoop();

        this.initDecoder();
        // if (this.videoMode == kVideoVodMode) {
        //     // 点播，先获取文件大小
        //     var req = {
        //         t: kGetFileInfoReq,
        //         u: url,
        //         p: this.downloadProto
        //     };
        //     this.downloadWorker.postMessage(req);
        // }
        // else {
        //     this.onGetFileInfo({
        //         sz: -1,
        //         st: 200
        //     });
        // }

        var self = this;
        this.registerVisibilityEvent(function (visible) {
            if (visible) {
                if (self.pcmPlayer)
                    self.pcmPlayer.resume();
                // self.resume();
            } else {
                if (self.pcmPlayer)
                    self.pcmPlayer.pause();
                // self.pause();
            }
        });

        this.startBuffering();
    } while (false);

    return ret;
};

Player.prototype.getTimestamp = function() {
    if (this.pcmPlayer)
        return this.pcmPlayer.getTimestamp() + this.timestamp_inc;
    return 0.0;
}

Player.prototype.setTimestampInc = function(inc) {
    this.timestamp_inc = inc;
    this.logger.logInfo("setTimestampInc " + inc);
}

// 直播时切换视频源
Player.prototype.changeSource = function (url) {
    if (this.fileInfo != null && this.fileInfo.url != url) {
        this.fileInfo.url = url;
        this.decodeWorker.postMessage({
            t: kChangeSourceReq,
            u: url
        });
    }
}

Player.prototype.pause = function () {
    if (this.playerState != playerStatePlaying)
        return;

    this.logger.logInfo("Pause.");

    this.decodeWorker.postMessage({
        t: kPauseDecodeReq,
    });

    //Do not stop downloader for background buffering.
    var ret = {
        e: 0,
        m: "Success"
    };

    this.playerState = playerStatePausing;
    if (this.onPause) {
        this.logger.logInfo("Pause. on pause ...");
        this.onPause();
    }
    return ret;
};

Player.prototype.resume = function (fromSeek) {
    if (this.playerState != playerStatePausing)
        return;

    this.logger.logInfo("Resume.");

    // if (!fromSeek) {
    //     //Resume audio context.
    //     this.pcmPlayer.resume();
    // }

    this.decodeWorker.postMessage({
        t: kResumeDecodeReq,
    });

    var ret = {
        e: 0,
        m: "Success"
    };
    this.playerState = playerStatePlaying;
    if (this.onPlaying) {
        this.onPlaying();
    }
    this.decoderRequesting = false;
    return ret;
};

Player.prototype.stop = function () {
    this.logger.logInfo("Stop.");
    if (this.playerState == playerStateIdle) {
        var ret = {
            e: -1,
            m: "Not playing"
        };
        return ret;
    }

    this.stopDownloadFile();
    this.stopTrackTimer();
    this.hideLoading();

    this.fileInfo = null;
    this.canvas = null;
    this.webglPlayer = null;
    this.callback = null;
    this.duration = 0;
    this.pixFmt = 0;
    this.videoWidth = 0;
    this.videoHeight = 0;
    this.yLength = 0;
    this.uvLength = 0;
    this.beginTimeOffset = 0;
    this.decoderState = decoderStateIdle;
    this.playerState = playerStateIdle;
    this.audioFrameBuffer = null;
    this.videoFrameBuffer = null;
    this.buffering = false;

    if (this.pcmPlayer) {
        this.pcmPlayer.destroy();
        this.pcmPlayer = null;
        this.logger.logInfo("Pcm player released.");
    }

    if (this.timeTrack) {
        this.timeTrack.value = 0;
    }
    if (this.timeLabel) {
        this.timeLabel.innerHTML = this.formatTime(0) + "/" + this.displayDuration;
    }

    if (this.onStop) {
        this.onStop();
    }

    this.logger.logInfo("Closing decoder.");
    this.decodeWorker.postMessage({
        t: kCloseDecoderReq
    });

    this.logger.logInfo("Uniniting decoder.");
    this.decodeWorker.postMessage({
        t: kUninitDecoderReq
    });

    return ret;
};

Player.prototype.seekTo = function (ms) {
    if (this.videoMode != kVideoVodMode)
        return;

    console.log("------- seekTo " + ms + " ---------");

    // Request decoder to seek.
    this.decodeWorker.postMessage({
        t: kSeekToReq,
        ms: ms
    });

    this.playerState = playerStatePausing;
    if (this.pcmPlayer)
        this.pcmPlayer.pause();

    // // Pause playing.
    // this.pause();

    // // Stop download.
    // this.stopDownloadFile();

    // // Clear frame buffer.
    // if (this.videoFrameBuffer != null)
    //     this.videoFrameBuffer.length = 0;
    // if (this.audioFrameBuffer != null)
    //     this.audioFrameBuffer.length = 0;


    // // Reset begin time offset.
    // this.beginTimeOffset = ms / 1000;
    // this.logger.logInfo("seekTo beginTimeOffset " + this.beginTimeOffset);

    // this.seeking = true;
    // this.justSeeked = true;
    // this.urgent = true;
    // this.seekReceivedLen = 0;
    // this.videoBaseTime = 0;
    // this.audioBaseTime = 0;
    // this.startBuffering();
};

Player.prototype.fullscreen = function () {
    if (this.webglPlayer) {
        this.webglPlayer.fullscreen();
    }
};

Player.prototype.getState = function () {
    return this.playerState;
};

Player.prototype.setTrack = function (timeTrack, timeLabel) {
    this.timeTrack = timeTrack;
    this.timeLabel = timeLabel;
    this.durationLabel = durationLabel;

    if (this.timeTrack) {
        var self = this;
        this.timeTrack.oninput = function () {
            if (!self.seeking) {
                self.seekTo(self.timeTrack.value);
            }
        }
        this.timeTrack.onchange = function () {
            if (!self.seeking) {
                self.seekTo(self.timeTrack.value);
            }
        }
    }
};

Player.prototype.initDecoder = function() {
    this.logger.logInfo("Initializing decoder.");
    var req = {
        t: kInitDecoderReq,
        s: -1,
        c: this.fileInfo.chunkSize,
        u: this.fileInfo.url,
        i: (this.videoMode == kVideoVodMode) ? 0 : 1    // isLive
    };
    this.decodeWorker.postMessage(req);
}

Player.prototype.onGetFileInfo = function (info) {
    if (this.playerState == playerStateIdle) {
        return;
    }

    this.logger.logInfo("Got file size rsp:" + info.st + " size:" + info.sz + " byte.");
    if (info.st == 200) {
        this.fileInfo.size = Number(info.sz);
        // 修改点播时的开始缓存数据大小
        if (this.fileInfo.size > 0 && this.waitBufferLength > this.fileInfo.size)
            this.waitBufferLength = this.fileInfo.size;

        this.logger.logInfo("Initializing decoder.");
        var req = {
            t: kInitDecoderReq,
            s: this.fileInfo.size,
            c: this.fileInfo.chunkSize,
            u: this.fileInfo.url,
            i: (this.videoMode == kVideoVodMode) ? 0 : 1    // isLive
        };
        this.decodeWorker.postMessage(req);

        if (this.downloadProto == kProtoHttp) {
            if (this.videoMode == kVideoVodMode)
                this.startDownloadFile(0, 1024*1024*2);  // 点播，从文件起始位置0开始下载
            else                
                this.startDownloadFile(-1, -1); // http直播
        }
    } else {
        this.reportPlayError(-1, info.st, "GetFileInfo Error");
    }
};

Player.prototype.onInitDecoder = function (objData) {
    if (this.playerState == playerStateIdle) {
        return;
    }

    this.logger.logInfo("Init decoder response " + objData.e + ".");
    if (objData.e == 0) {
        this.decoderState = decoderStateInitializing;
        this.decoderRequesting = false;
    } else {
        this.reportPlayError(objData.e, -1, "InitDecoder Error");
    }
};

Player.prototype.onOpenDecoder = function (objData) {
    if (this.playerState == playerStateIdle) {
        return;
    }

    // this.logger.logInfo("Open decoder response " + objData.e + ".");
    if (objData.e == 0) {
        this.onVideoParam(objData.v);
        this.onAudioParam(objData.a);
        this.decoderState = decoderStateReady;
        this.logger.logInfo("Decoder ready now.");
    }
    else if (objData.e != 11 && objData.e != 2) { // 11 = Err_NeedMoreData, 2 = Invalidate State
        this.reportPlayError(-1, objData.e, "Open Decoder Failed.");
        this.stop();
    }
    this.decoderRequesting = false;
};

Player.prototype.onDecodeRsp = function(errcode, max_ts) {
    if (this.playerState != playerStatePlaying)
        return;

    this.decoderRequesting = false;
    switch (errcode) {
    case ErrorSuccess:              // 成功解码1帧视频
        this.max_timestamp = max_ts;
        if (this.videoMode == kVideoLiveMode) {
            // 直播模式，检查缓存帧是否过多，并启动加速播放功能
            if (!this.play_accelerate && this.max_timestamp - this.curr_timestamp >= this.accelerate_start_time) {
                this.play_accelerate = true;
                this.audio_skipframes = 0;
                this.video_skipframes = 0;
                this.skip_count = 0;
                this.logger.logInfo("[PLAYER] start acceleration");
            }
        }
        this.requestDecode();       // 再次调用解码
        break;
    case ErrorOldFrame:             // 缓冲太多数据，自动跳转到最新关键帧位置继续播放
        this.logger.logInfo("Decoder buffer full, reset player!!");
        this.resetPlayer();
        this.requestDecode();       // 再次调用解码
        break;
    case ErrorEof:     // EOF
        this.decoderState == decoderStateFinished;
        this.logger.logInfo("Decoder finished.");
        break;
    case ErrorNeedMoreData:
        break;
    default:
        this.logger.logInfo("Decode Error: " + errcode);
        this.reportPlayError(-1, errcode, "Decode Error");
        this.stop();
        break;
    }
};

Player.prototype.onChangeSourceRsp = function(errcode) {
    if (errcode == 0) {
        this.videoBaseTime = 0;
        this.audioBaseTime = 0;
        this.audioLastFrameTime = 0;
        if (this.videoFrameBuffer != null)
            this.videoFrameBuffer.length = 0;
        if (this.audioFrameBuffer != null)
            this.audioFrameBuffer.length = 0;

        this.startBuffering();
    }
}

Player.prototype.onPauseDecodeRsp = function() {
    //Pause video rendering and audio flushing.
    this.playerState = playerStatePausing;

    // //Pause audio context.
    // if (this.pcmPlayer) {
    //     this.pcmPlayer.pause();
    // }

    //Stop track timer.
    this.stopTrackTimer();
};

Player.prototype.onResumeDecodeRsp = function() {
    //Restart video rendering and audio flushing.
    this.playerState = playerStatePlaying;
    //Restart track timer.
    // if (!this.seeking) {
    this.startTrackTimer();
    // }
};

Player.prototype.onFileData = function (data, start, end, seq) {
    // this.logger.logInfo("Got data bytes=" + start + "-" + end + ". byteLength = " + data.byteLength);
    if (this.playerState == playerStateIdle) {
        return;
    }

    if (this.videoMode == kVideoVodMode)
        this.fileInfo.offset = start + data.byteLength;
    else
        this.fileInfo.offset += data.byteLength;
    if (this.buffering)
        this.fileInfo.bufferedLength += data.byteLength;

    var objData = {
        t: kFeedDataReq,
        o: start,
        d: data,
        s: seq,
    };
    this.decodeWorker.postMessage(objData, [objData.d]);
};

Player.prototype.onDownloadStopped = function(offset, count, status, seqno) {
    if (this.playerState == playerStateIdle)
        return;

    // if (status == 416 && count > 0) {
    //     this.logger.logInfo("try to download again, offset " + offset + " available 0, seqno " + seqno);
    //     this.startDownloadFile(offset, 0, seqno);
    //     return;
    // }

    if(status == 200 && this.videoMode != kVideoVodMode) {
        if(this.onLiveFinish) {
            this.onLiveFinish();
        }
        return;
    }

    // 通知Decoder下载已停止
    this.decodeWorker.postMessage( {
        t: kDownloadStopped,
        o: offset,
        s: status,
        n: seqno
    });

    if (status == 404 || status >= 500) {
        console.log("DownloadFile error: " + status);
        this.reportPlayError(-1, -1, "Download File Error!");
        this.stop();
    }
}

Player.prototype.onVideoParam = function (v) {
    if (this.playerState == playerStateIdle) {
        return;
    }

    this.logger.logInfo("Video param duation:" + v.d + " pixFmt:" + v.p + " width:" + v.w + " height:" + v.h + ".");
    if (v.d > 0)
        this.duration = v.d - 50;
    this.pixFmt = v.p;
    //this.canvas.width = v.w;
    //this.canvas.height = v.h;
    this.videoWidth = v.w;
    this.videoHeight = v.h;
    this.yLength = this.videoWidth * this.videoHeight;
    this.uvLength = (this.videoWidth / 2) * (this.videoHeight / 2);
    this.videoFrameBuffer = [];

    /*
    //var playCanvasContext = playCanvas.getContext("2d"); //If get 2d, webgl will be disabled.
    this.webglPlayer = new WebGLPlayer(this.canvas, {
        preserveDrawingBuffer: false
    });
    */

    if (this.timeTrack) {
        this.timeTrack.min = 0;
        this.timeTrack.max = this.duration;
        this.timeTrack.value = 0;
    }
    this.displayDuration = this.formatTime(this.duration / 1000);

    // var byteRate = 1000 * this.fileInfo.size / this.duration;
    // var targetSpeed = downloadSpeedByteRateCoef * byteRate;
    // var chunkPerSecond = targetSpeed / this.fileInfo.chunkSize;
    // this.chunkInterval = 1000 / chunkPerSecond;
    // this.seekWaitLen = byteRate * maxBufferTimeLength * 2;
    // this.logger.logInfo("Seek wait len " + this.seekWaitLen);

    // this.logger.logInfo("Byte rate:" + byteRate + " target speed:" + targetSpeed + " chunk interval:" + this.chunkInterval + ".");
};

Player.prototype.onAudioParam = function (a) {
    if (this.playerState == playerStateIdle || a.f == -1) {
        return;
    }

    this.logger.logInfo("Audio param sampleFmt:" + a.f + " channels:" + a.c + " sampleRate:" + a.r + ".");

    var sampleFmt = a.f;
    var channels = a.c;
    var sampleRate = a.r;

    var encoding = "16bitInt";
    switch (sampleFmt) {
        case 0:
            encoding = "8bitInt";
            break;
        case 1:
            encoding = "16bitInt";
            break;
        case 2:
            encoding = "32bitInt";
            break;
        case 3:
            encoding = "32bitFloat";
            break;
        default:
            encoding = null;
            this.logger.logError("Unsupported audio sampleFmt " + sampleFmt + "!");
        // return;
    }

    this.audioEncoding = encoding;
    this.audioChannels = channels;
    this.audioSampleRate = sampleRate;
    if (encoding == null)
        return; // no audio

    this.logger.logInfo("Audio encoding " + encoding + ".");

    this.audioFrameBuffer = [];
    this.pcmPlayer = new PCMPlayer({
        encoding: encoding,
        channels: channels,
        sampleRate: sampleRate,
        flushingTime: 5000
    });

    if(this.muteCallback) {
        this.muteCallback(this.pcmPlayer.isMute() ? true : false);
    }
};

Player.prototype.audioContextUnSuspend = function() {
    this.pcmPlayer.resume();
};

Player.prototype.audioPause = function() {
    this.pcmPlayer.pause();
};

Player.prototype.isMute = function(muteCallback) {
    if(muteCallback) {
        this.muteCallback = muteCallback;
    }

    if(this.pcmPlayer) {
        return this.pcmPlayer.isMute() ? true : false;
    }
    else {
        return false;
    }
};

Player.prototype.restartAudio = function () {
    if (this.pcmPlayer) {
        this.pcmPlayer.destroy();
        this.pcmPlayer = null;
    }

    if (this.audioEncoding == null)
        return;

    this.pcmPlayer = new PCMPlayer({
        encoding: this.audioEncoding,
        channels: this.audioChannels,
        sampleRate: this.audioSampleRate,
        flushingTime: 5000
    });
};

Player.prototype.onAudioFrame = function (frame) {
    if (this.decoderState != decoderStateReady || this.audioFrameBuffer == null)
        return;
    this.audioFrameBuffer.push(frame);
    // this.logger.logInfo("onAudioFrame -- " + frame.s + " | " + this.audioFrameBuffer.length);
};

Player.prototype.onVideoFrame = function (frame) {
    if (this.decoderState != decoderStateReady || this.videoFrameBuffer == null)
        return;
    this.videoFrameBuffer.push(frame);
    // this.logger.logInfo("onVideoFrame -- " + frame.s + " | " + this.videoFrameBuffer.length);

    if (this.buffering) {
        this.stopBuffering();
    }
};

Player.prototype.displayAudioFrame = function (frame) {
    if (this.playerState != playerStatePlaying || this.pcmPlayer == null) {
        return false;
    }

    var nowtime = new Date().getTime();
    if (this.audioBaseTime == 0) {
        this.audioBaseTime = nowtime;
        this.audioBaseFrameTime = frame.s;
        this.audioLastFrameTime = 0;
        this.setTimestampInc(0.0 - this.pcmPlayer.getTimestamp());
    }

    var ts = frame.s - this.audioBaseFrameTime - this.audio_timestamp_inc;
    var pts = parseInt(this.getTimestamp() * 1000);
    if (ts - 30 <= pts) {
        // this.logger.logInfo("displayAudioFrame: " + frame.s 
        //     + ", tpass " + (nowtime - this.audioBaseTime)
        //     + ", frameTimestamp " + ts + " [" + this.audio_timestamp_inc + "] "
        //     + ", currTimestamp " + pts);

        this.audioLastFrameTime = frame.s;
        if (!this.pcmPlayer.isMute()) {
            this.pcmPlayer.play(new Uint8Array(frame.d));
        }
        return true;
    }

    return false;
};

Player.prototype.displayVideoFrame = function (frame) {
    if (this.playerState != playerStatePlaying) {
        return false;
    }

    if (this.videoMode == kVideoRealtimeLiveMode) {
        // 实时直播方式，不考虑每帧的时间间隔，直接播放
        this.renderVideoFrame(new Uint8Array(frame.d));
        return true;
    }

    var nowtime = new Date().getTime();
    if (this.videoBaseTime == 0) {
        this.videoBaseTime = nowtime;
        this.videoBaseFrameTime = frame.s;
        if (!this.pcmPlayer)
            this.setTimestampInc(0.0);
    }

    var ts = frame.s - this.videoBaseFrameTime - this.video_timestamp_inc;
    var pts = parseInt(this.getTimestamp() * 1000);
    if (ts <= pts) {
    // if (frame.s <= this.audioLastFrameTime) {
        // this.logger.logInfo("displayVideoFrame: " + frame.s 
        //     + " ==> " + ts +  " [" + this.video_timestamp_inc + "] "
        //     + ", audioLast " + (this.audioLastFrameTime - this.videoBaseFrameTime - this.audio_timestamp_inc)
        //     + ", currTime " + pts);

        this.renderVideoFrame(new Uint8Array(frame.d));
        this.curr_timestamp = frame.s;
        this.lastDisplayTime = nowtime;

        if (this.duration > 0 && frame.s >= this.duration)
            this.decoderState = decoderStateFinished;
        return true;
    }

    return false;
};

Player.prototype.displayLoop = function () {
    if (this.playerState !== playerStateIdle) {
        requestAnimationFrame(this.displayLoop.bind(this));
    }
    if (this.playerState != playerStatePlaying) {
        return;
    }

    if (this.buffering || this.videoFrameBuffer == null) {
        this.requestDecode();
        return;
    }

    if (this.audioFrameBuffer == null) {
        // 只有视频
        if (this.videoFrameBuffer.length > 0 && this.displayVideoFrame(this.videoFrameBuffer[0]))
            this.videoFrameBuffer.shift();
    }
    else {
        // requestAnimationFrame may be 60fps, if stream fps too large,
        // we need to render more frames in one loop, otherwise display
        // fps won't catch up with source fps, leads to memory increasing,
        // set to 2 now.
        if (this.videoFrameBuffer.length > 0) {
            if (this.displayVideoFrame(this.videoFrameBuffer[0])) {
                this.videoFrameBuffer.shift();
            }
        }
        if (this.audioFrameBuffer.length > 0) {
            if (this.displayAudioFrame(this.audioFrameBuffer[0])) {
                this.audioFrameBuffer.shift();

                if (this.play_accelerate) {
                    // 直播加速播放，减少延时
                    if (this.skip_count == 0) {
                        if (this.videoFrameBuffer.length >= this.accelerate_skip_frames 
                                    && this.audioFrameBuffer.length >= this.accelerate_skip_frames) {
                            var skip_frames = this.accelerate_skip_frames;
                            var ts = this.audioLastFrameTime;
                            var audio_skip_frames = 0;
                            var video_skip_frames = 0;
                            while (skip_frames-- > 0 && this.audioFrameBuffer.length > 0) {
                                ts = this.audioFrameBuffer[0].s;
                                this.audioFrameBuffer.shift();
                                audio_skip_frames++;
                            }
                            var inc_ts = ts - this.audioLastFrameTime;
                            this.audioLastFrameTime = ts;
                            this.audio_timestamp_inc += inc_ts;

                            ts = this.curr_timestamp;
                            inc_ts = 0;
                            while (this.video_timestamp_inc + inc_ts < this.audio_timestamp_inc 
                                    && this.videoFrameBuffer.length > 0) {
                                ts = this.videoFrameBuffer[0].s;
                                this.videoFrameBuffer.shift();
                                video_skip_frames++;
                                inc_ts = ts - this.curr_timestamp;
                            }
                            this.curr_timestamp = ts;
                            this.video_timestamp_inc += inc_ts;
    
                            this.skip_count = this.accelerate_play_frames;
                            this.audio_skipframes += audio_skip_frames;
                            this.video_skipframes += video_skip_frames;
                    
                            // this.logger.logInfo("[PLAYER] skip frames: audio=" + audio_skip_frames + ", video=" + video_skip_frames);

                            if (this.max_timestamp - this.curr_timestamp <= this.accelerate_stop_time) {
                                this.play_accelerate = false;
                                this.logger.logInfo("[PLAYER] stop acceleration. curr_time " + this.curr_timestamp 
                                    + "," + this.audioLastFrameTime + ", audio skip " 
                                    + this.audio_skipframes + " frames, inc " + this.audio_timestamp_inc + " ms, video skip " 
                                    + this.video_skipframes + " frames, inc " + this.video_timestamp_inc + " ms");
                            }
                        }
                    }
                    else
                        this.skip_count--;
                }        
            }
        }
    }

    if(this.isFirst && this.videoBaseTime > 0 && this.audioBaseTime > 0) {
        this.isFirst = 0;

        if (this.onReadyToPlay) {
            this.onReadyToPlay(this.duration);
        }
    }

    if (this.decoderState == decoderStateFinished) {
        // 解码已结束，播放完音视频帧就停止播放
        if (this.videoFrameBuffer.length == 0 ||  (this.audioFrameBuffer != null && this.audioFrameBuffer.length == 0)) {
            this.reportPlayError(this.decoderError, 0, "Finished");
            this.stop();
        }
    }
    else {
        this.requestDecode();
    }
};

Player.prototype.renderVideoFrame = function (data) {
    this.webglPlayer.renderFrame(data, this.videoWidth, this.videoHeight, this.yLength, this.uvLength);
};

Player.prototype.resetPlayer = function() {
    // Clear frame buffer.
    if (this.videoFrameBuffer != null)
        this.videoFrameBuffer.length = 0;
    if (this.audioFrameBuffer != null)
        this.audioFrameBuffer.length = 0;

    // this.videoBaseTime = 0;
    // this.audioBaseTime = 0;
}

Player.prototype.onSeekToRsp = function (ret, ms) {
    // console.log("onSeekToRsp: " + ret +  ", " + this.playerState + ", " + this.decoderState);
    this.playerState = playerStatePlaying;
    if (ret == 0) {
        if (this.pcmPlayer)
            this.setTimestampInc((ms / 1000.0) - this.pcmPlayer.getTimestamp());
        this.resetPlayer();
        this.startBuffering();
    }
};

Player.prototype.onRequestData = function (offset, available, seqno) {
    console.log("============= onRequestData ===============");
    this.stopDownloadFile();
    if (offset == -1 && available == -1)
        return;
    
    this.logger.logInfo("Request data, offset " + offset + ", available " + available + ", seqno " + seqno);
    this.startDownloadFile(offset, available, seqno);
};

Player.prototype.requestDecode = function() {
    if (this.decoderRequesting || this.playerState == playerStateIdle)
        return;

    if (this.decoderState == decoderStateInitializing) {
        if (!this.buffering || this.fileInfo.bufferedLength >= this.waitBufferLength) {
            // this.logger.logInfo("requestDecode(): Request open decoder...");
            // 打开解码器
            this.decoderRequesting = true;
            this.decodeWorker.postMessage({
                t: kOpenDecoderReq,
            });
        }
    }
    else if (this.decoderState == decoderStateReady) {
        // 判断是否需要解码
        if (this.videoFrameBuffer.length < 10 || (this.audioFrameBuffer != null && this.audioFrameBuffer.length < 10)) {
            // this.logger.logInfo("requestDecode(): Request decode...");
            // 请求解码
            this.decoderRequesting = true;
            this.decodeWorker.postMessage({
                t: kDecodeReq,
            });
        }
    }
};

Player.prototype.startBuffering = function () {
    this.buffering = true;
    this.fileInfo.bufferedLength = 0;
    if (this.pcmPlayer)
        this.pcmPlayer.pause();
    this.showLoading();

    this.decoderRequesting = false;
    this.requestDecode();

    if (this.onLoadstart) {
        console.log("start buffering on load start")
        this.onLoadstart();
    }
}

Player.prototype.stopBuffering = function () {
    this.buffering = false;
    if (this.pcmPlayer)
        this.pcmPlayer.resume();
    this.hideLoading();

    if (this.onPlaying) {
        console.log("stop buffering on playing")
        this.onPlaying();
    }
}


Player.prototype.startDownloadFile = function(start, count, seqno) {
    if (this.downloadProto != kProtoRtmp) {
        this.fileInfo.offset = start;
        this.downloadOffset = start;
        this.downloadSeqNo = seqno;
        var req = {
            t: kDownloadFileReq,
            u: this.fileInfo.url,
            s: start,
            e: count,
            q: this.downloadSeqNo,
            p: this.downloadProto,
            c: this.fileInfo.chunkSize,
        };
        this.downloadWorker.postMessage(req);
    }
}

Player.prototype.stopDownloadFile = function() {
    if (this.downloadProto != kProtoRtmp) {
        this.downloadSeqNo++;
        this.downloadWorker.postMessage( {
            t: kCloseDownloaderReq,
        });
    }
}

Player.prototype.startTrackTimer = function () {
    var self = this;
    this.trackTimer = setInterval(function () {
        self.updateTrackTime();
    }, this.trackTimerInterval);
};

Player.prototype.stopTrackTimer = function () {
    if (this.trackTimer != null) {
        clearInterval(this.trackTimer);
        this.trackTimer = null;
    }
};

Player.prototype.updateTrackTime = function () {
    if (this.buffering) return;
    if (this.playerState == playerStatePlaying && this.pcmPlayer) {
        var currentPlayTime = this.getTimestamp();// + this.beginTimeOffset;
        if (this.timeTrack) {
            this.timeTrack.value = 1000 * currentPlayTime;
        }

        if (this.timeLabel) {
            this.timeLabel.innerHTML = this.formatTime(currentPlayTime);// + "/" + this.displayDuration;
        }

        if (this.durationLabel) {
            this.durationLabel.innerHTML = this.formatTime(this.duration / 1000);
        }

        if (this.onTimeupdate) {
            this.onTimeupdate(currentPlayTime * 1000, this.duration);
        }
    }
};

Player.prototype.formatTime = function (s) {
    var h = Math.floor(s / 3600) < 10 ? '0' + Math.floor(s / 3600) : Math.floor(s / 3600);
    var m = Math.floor((s / 60 % 60)) < 10 ? '0' + Math.floor((s / 60 % 60)) : Math.floor((s / 60 % 60));
    var s = Math.floor((s % 60)) < 10 ? '0' + Math.floor((s % 60)) : Math.floor((s % 60));
    return result = h + ":" + m + ":" + s;
};

Player.prototype.reportPlayError = function (error, status, message) {
    var e = {
        error: error || 0,
        status: status || 0,
        message: message || ""
    };

    if (this.callback) {
        this.callback(e);
    }
};

Player.prototype.setLoadingDiv = function (loadingDiv) {
    this.loadingDiv = loadingDiv;
}

Player.prototype.hideLoading = function () {
    if (this.loadingDiv != null) {
        loading.style.display = "none";
    }
};

Player.prototype.showLoading = function () {
    if (this.loadingDiv != null) {
        loading.style.display = "block";
    }
};

Player.prototype.registerVisibilityEvent = function (cb) {
    var hidden = "hidden";

    // Standards:
    if (hidden in document) {
        document.addEventListener("visibilitychange", onchange);
    } else if ((hidden = "mozHidden") in document) {
        document.addEventListener("mozvisibilitychange", onchange);
    } else if ((hidden = "webkitHidden") in document) {
        document.addEventListener("webkitvisibilitychange", onchange);
    } else if ((hidden = "msHidden") in document) {
        document.addEventListener("msvisibilitychange", onchange);
    } else if ("onfocusin" in document) {
        // IE 9 and lower.
        document.onfocusin = document.onfocusout = onchange;
    } else {
        // All others.
        window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange;
    }

    function onchange(evt) {
        var v = true;
        var h = false;
        var evtMap = {
            focus: v,
            focusin: v,
            pageshow: v,
            blur: h,
            focusout: h,
            pagehide: h
        };

        evt = evt || window.event;
        var visible = v;
        if (evt.type in evtMap) {
            visible = evtMap[evt.type];
        } else {
            visible = this[hidden] ? h : v;
        }
        cb(visible);
    }

    // set the initial state (but only if browser supports the Page Visibility API)
    if (document[hidden] !== undefined) {
        onchange({ type: document[hidden] ? "blur" : "focus" });
    }
}
